using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using GameLib.Enums;
using GameLib.GameObjects;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace GameLib.Managers
{
    public class PieceManager
    {
        List<Piece> pieces; // Actual Pieces
        Vector2[,] matrix; // Positions of Pieces
        Texture2D tron;
        Texture2D shade;

        SoundEffect sound_MovingPiece;
        SoundEffectInstance sound_MovingPieceInstance;

        SpriteFont spriteFont;
        Point spriteSize;

        // DEBUG-VARIABLES
        // ----------------------
        string testText = "";
        Texture2D collisionBox;
        string moveMadeString = "";
        // ----------------------

        int msSinceLastMove;
        int moveSpeed = 50;
        Random random;
        int shuffles = 50;
        int shufflesMade;
        bool shuffled;

        List<Direction> moveMade;
        List<Direction> movesToMake;
        bool gameAutoComplete;

        public event EventHandler GameFinished;
        public event EventHandler ShuffleFinished;

        public PieceManager(ContentManager content)
        {
            pieces = new List<Piece>();
            matrix = new Vector2[4, 4];
            spriteSize = new Point(150, 100);

            spriteFont = content.Load<SpriteFont>("InfoFont");


            tron = content.Load<Texture2D>("tron");
            //tron = background;
            collisionBox = content.Load<Texture2D>("collisionbox");
            shade = content.Load<Texture2D>("pieceShaded");

            sound_MovingPiece = content.Load<SoundEffect>("movingPiece");
            sound_MovingPieceInstance = sound_MovingPiece.CreateInstance();

            moveMade = new List<Direction>();
            movesToMake = new List<Direction>();
            gameAutoComplete = false;

            random = new Random();
            msSinceLastMove = -2000;
            shufflesMade = 0;
            shuffled = false;
        }

        public void UpdateBackground(Texture2D camera)
        {
            tron = camera;
        }

        #region Print

        private void PrintInfo()
        {
            testText = "";
            PrintPiecesPosition();
            PrintBlankPiecePosition();
            PrintMatrixInfo();
            //MarkMovablePieces();
        }

        public void PrintPiecesPosition()
        {
            foreach (var piece in pieces)
            {
                testText += "Piece " + piece.ID + " position is " + piece.Position + "\n";
            }
        }

        public void PrintBlankPiecePosition()
        {
            for (int x = 0; x < matrix.GetLength(0); x++)
            {
                for (int y = 0; y < matrix.GetLength(1); y++)
                {
                    var blank = true;
                    foreach (var piece in pieces)
                    {
                        if (piece.NewPosition == matrix[x, y])
                        {
                            blank = false;
                            break;
                        }
                    }
                    if (blank)
                        testText += "Blank position is at matrix(" + x + ", " + y + ") " + matrix[x, y] + "\n";
                }
            }
        }

        private void PrintMatrixInfo()
        {
            testText += "LB0: " + matrix.GetLowerBound(0) + " LB1: " + matrix.GetLowerBound(1);
            testText += " UB0: " + matrix.GetUpperBound(0) + " UB1: " + matrix.GetUpperBound(1);
        }
        #endregion

        /// <summary>
        /// Gets the Point where the blank is
        /// </summary>
        public Point BlankPiecePosition()
        {
            Point blankPoint = new Point();
            for (int x = 0; x < matrix.GetLength(0); x++)
            {
                for (int y = 0; y < matrix.GetLength(1); y++)
                {
                    var blank = true;
                    foreach (var piece in pieces)
                    {
                        if (piece.NewPosition == matrix[x, y])
                        {
                            blank = false;
                            break;
                        }
                    }
                    if (blank)
                        blankPoint = new Point(x, y);
                }
            }
            return blankPoint;
        }

        private void MarkMovablePieces()
        {
            var blank = BlankPiecePosition();

            foreach (var piece in pieces)
            {
                piece.Color = Color.White;
                // Above blank
                if (blank.Y - 1 >= matrix.GetLowerBound(1))
                {
                    if (piece.NewPosition == matrix[blank.X, blank.Y - 1])
                    {
                        piece.Color = Color.Green;
                        continue;
                    }

                }
                // Under blank
                if (blank.Y + 1 <= matrix.GetUpperBound(1))
                {
                    if (piece.NewPosition == matrix[blank.X, blank.Y + 1])
                    {
                        piece.Color = Color.Green;
                        continue;
                    }
                }

                // Right of blank
                if (blank.X + 1 <= matrix.GetUpperBound(0))
                {
                    if (piece.NewPosition == matrix[blank.X + 1, blank.Y])
                    {
                        piece.Color = Color.Green;
                        continue;
                    }
                }
                // Left of blank
                if (blank.X - 1 >= matrix.GetLowerBound(0))
                {
                    if (piece.NewPosition == matrix[blank.X - 1, blank.Y])
                    {
                        piece.Color = Color.Green;
                        continue;
                    }
                }

            }
        }

        private List<Direction> PossibleDirections()
        {
            var blank = BlankPiecePosition();
            var directions = new List<Direction>();

            foreach (var piece in pieces)
            {
                // Above blank
                if (blank.Y - 1 >= matrix.GetLowerBound(1))
                {
                    if (piece.NewPosition == matrix[blank.X, blank.Y - 1])
                    {
                        directions.Add(Direction.Down);
                        continue;
                    }

                }
                // Under blank
                if (blank.Y + 1 <= matrix.GetUpperBound(1))
                {
                    if (piece.NewPosition == matrix[blank.X, blank.Y + 1])
                    {
                        directions.Add(Direction.Up);
                        continue;
                    }
                }

                // Right of blank
                if (blank.X + 1 <= matrix.GetUpperBound(0))
                {
                    if (piece.NewPosition == matrix[blank.X + 1, blank.Y])
                    {
                        directions.Add(Direction.Left);
                        continue;
                    }
                }
                // Left of blank
                if (blank.X - 1 >= matrix.GetLowerBound(0))
                {
                    if (piece.NewPosition == matrix[blank.X - 1, blank.Y])
                    {
                        directions.Add(Direction.Right);
                        continue;
                    }
                }
            }
            return directions;
        }

        private Direction PossibleDirection(Piece piece)
        {
            var blank = BlankPiecePosition();

            // Above blank
            if (blank.Y - 1 >= matrix.GetLowerBound(1))
            {
                if (piece.NewPosition == matrix[blank.X, blank.Y - 1])
                {
                    return Direction.Down;
                }

            }
            // Under blank
            if (blank.Y + 1 <= matrix.GetUpperBound(1))
            {
                if (piece.NewPosition == matrix[blank.X, blank.Y + 1])
                {
                    return Direction.Up;
                }
            }

            // Right of blank
            if (blank.X + 1 <= matrix.GetUpperBound(0))
            {
                if (piece.NewPosition == matrix[blank.X + 1, blank.Y])
                {
                    return Direction.Left;
                }
            }
            // Left of blank
            if (blank.X - 1 >= matrix.GetLowerBound(0))
            {
                if (piece.NewPosition == matrix[blank.X - 1, blank.Y])
                {
                    return Direction.Right;
                }
            }
            return Direction.None;
        }

        public bool MovePiece(Direction direction)
        {
            var blank = BlankPiecePosition();
            foreach (var piece in pieces)
            {
                piece.Color = Color.White;

                switch (direction)
                {
                    case Direction.Left:
                        // Right of blank
                        if (blank.X + 1 <= matrix.GetUpperBound(0))
                        {
                            if (piece.Position == matrix[blank.X + 1, blank.Y])
                            {
                                piece.ChangePosition(matrix[blank.X, blank.Y]);
                                sound_MovingPiece.Play(0.3f, 0.0f, 0.0f);
                                moveMade.Add(Direction.Left);
                                return true;
                            }
                        }
                        break;
                    case Direction.Right:
                        // Left of blank
                        if (blank.X - 1 >= matrix.GetLowerBound(0))
                        {
                            if (piece.Position == matrix[blank.X - 1, blank.Y])
                            {
                                piece.ChangePosition(matrix[blank.X, blank.Y]);
                                sound_MovingPiece.Play(0.3f, 0.0f, 0.0f);
                                moveMade.Add(Direction.Right);
                                return true;
                            }
                        }
                        break;
                    case Direction.Up:
                        // Under blank
                        if (blank.Y + 1 <= matrix.GetUpperBound(1))
                        {
                            if (piece.Position == matrix[blank.X, blank.Y + 1])
                            {
                                piece.ChangePosition(matrix[blank.X, blank.Y]);
                                sound_MovingPiece.Play(0.3f, 0.0f, 0.0f);
                                moveMade.Add(Direction.Up);
                                return true;
                            }
                        }
                        break;
                    case Direction.Down:
                        // Above blank
                        if (blank.Y - 1 >= matrix.GetLowerBound(1))
                        {
                            if (piece.Position == matrix[blank.X, blank.Y - 1])
                            {
                                piece.ChangePosition(matrix[blank.X, blank.Y]);
                                sound_MovingPiece.Play(0.3f, 0.0f, 0.0f);
                                moveMade.Add(Direction.Down);
                                return true;
                            }
                        }
                        break;
                }
            }
            //moveMade.Add(Direction.None);
            return false;
        }

        public bool MovePieceWithMouse(Vector2 mousePosition)
        {
            foreach (var piece in pieces)
            {
                piece.Color = Color.White;
                var mouse = new Rectangle((int)mousePosition.X + 15, (int)mousePosition.Y, 2, 2);

                if (piece.Bounds.Intersects(mouse))
                {
                    return MovePiece(PossibleDirection(piece));
                }

            }
            return false;
        }

        /// <summary>
        /// Fills the Matrix with one new Piece for each space, exept the last
        /// </summary>
        public void FillMatrix()
        {
            int nr = 0;
            for (int x = 0; x < matrix.GetLength(0); x++)
            {
                for (int y = 0; y < matrix.GetLength(1); y++)
                {
                    matrix[x, y] = new Vector2(70 + x * (spriteSize.X + 5), y * (spriteSize.Y + 5) + 100);
                    if (nr < 15)
                    {
                        pieces.Add(new Piece(tron, shade, nr, new Point(x, y), spriteSize, matrix[x, y]));
                    }

                    nr++;
                }
            }
            SoundEffect.MasterVolume = 0.1f;
        }

        private void CheckIfGameFinished()
        {
            foreach (var piece in pieces)
            {
                if (piece.OriginalPosition != piece.Position)
                {
                    return;
                }
            }
            Debug.WriteLine("PieceMgr: GameFinished!");
            if (GameFinished != null)
                GameFinished(this, new EventArgs());
            gameAutoComplete = false;
        }

        public void Update(TimeSpan elapsedGameTime)
        {
            //moveMadeString = "";
            //var moveMadeOutput = moveMade.ToList();
            //moveMadeOutput.Reverse();
            //foreach (var item in moveMadeOutput)
            //{
            //    moveMadeString += Enum.GetName(typeof(Direction), item) + "\n";
            //}

            if (shufflesMade < shuffles)
                InitShuffle(elapsedGameTime);
            else
                CheckIfGameFinished();



            PrintInfo();

            foreach (var piece in pieces)
            {
                piece.Update(elapsedGameTime);
            }

            if (gameAutoComplete)
                MakeMove(elapsedGameTime);

            //if (CheckIfGameFinished() && shufflesMade >= shuffles)
            //{
            //    if (GameFinished != null)
            //        GameFinished(this, new EventArgs());
            //}

        }

        /// <summary>
        /// Shuffles the pieces visibly for the player.
        /// </summary>
        /// <param name="gameTime">The game's internal time</param>
        private void InitShuffle(TimeSpan elapsedGameTime)
        {
            msSinceLastMove += elapsedGameTime.Milliseconds;
            if (msSinceLastMove > moveSpeed / 5)
            {
                msSinceLastMove = -50;
                var directions = PossibleDirections();
                if (directions.Count > 0)
                {
                    Direction direction = directions[random.Next(directions.Count)];
                    MovePiece(direction);
                    shufflesMade++;
                }
            }
            if (shufflesMade == shuffles)
            {
                SoundEffect.MasterVolume = 1.0f;
                Debug.WriteLine("PieceMgr: ShuffleFinished!");
                if (ShuffleFinished != null)
                    ShuffleFinished(this, new EventArgs());
            }

        }

        public void FastForwardPressed()
        {

            ReverseMoves();
            moveMade.Reverse();
            msSinceLastMove = -100;
            movesToMake = moveMade.ToList();
            gameAutoComplete = true;
        }

        private void ReverseMoves()
        {
            testText += "\nReverseMoves()\n";
            var reversedList = new List<Direction>();
            foreach (var move in moveMade)
            {
                testText += Enum.GetName(typeof(Direction), move) + " (";
                switch (move)
                {
                    case Direction.Left:
                        reversedList.Add(Direction.Right);
                        testText += Enum.GetName(typeof(Direction), Direction.Right) + ")\n";
                        break;
                    case Direction.Right:
                        reversedList.Add(Direction.Left);
                        testText += Enum.GetName(typeof(Direction), Direction.Left) + ")\n";
                        break;
                    case Direction.Up:
                        reversedList.Add(Direction.Down);
                        testText += Enum.GetName(typeof(Direction), Direction.Down) + ")\n";
                        break;
                    case Direction.Down:
                        reversedList.Add(Direction.Up);
                        testText += Enum.GetName(typeof(Direction), Direction.Up) + ")\n";
                        break;
                    case Direction.None:
                        reversedList.Add(Direction.None);
                        testText += Enum.GetName(typeof(Direction), Direction.None) + ")\n";
                        break;
                }
            }
            moveMade = reversedList;
        }

        public void MakeMove(TimeSpan elapsedGameTime)
        {
            msSinceLastMove += elapsedGameTime.Milliseconds;
            if (msSinceLastMove > moveSpeed / 5)
            {
                msSinceLastMove = -200;
                //if (movesToMake.Count == shuffles)
                //    testText += "\nMakeMove:\n";
                if (movesToMake.Count > 0)
                {
                    Direction direction = movesToMake.ElementAt(0);
                    MovePiece(direction);
                    movesToMake.RemoveAt(0);
                    //testText += Enum.GetName(typeof(Direction), direction) + " ";
                    //if (movesToMake.Count == 0)
                    //    msSinceLastMove = -5000;
                }


            }
        }

        public void Draw(SpriteBatch spriteBatch)
        {
            spriteBatch.DrawString(spriteFont, testText, new Vector2(700, 50), Color.Yellow);
            spriteBatch.DrawString(spriteFont, moveMadeString, new Vector2(1200, 2), Color.Yellow);
            foreach (var piece in pieces)
            {
                piece.Draw(spriteBatch);
                //spriteBatch.Draw(collisionBox, piece.Bounds, Color.White);
            }
        }
    }
}
